package com.digitalchina.frame.core.spring;


import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.digitalchina.frame.core.event.Event;

public class SpringContainerContext implements IContainerContext {

	private static Log log = LogFactory.getLog(SpringContainerContext.class);

	private ServletContext servletContext;

	private ApplicationContext applicationContext;

	private BucketListableBeanFactory beanFactory;

	public SpringContainerContext() {
		log = LogFactory.getLog(getClass());
		beanFactory = new BucketListableBeanFactory(null);
	}

	public void setServletContext(ServletContext context) {
		servletContext = context;
		setApplicationContext(WebApplicationContextUtils.getWebApplicationContext(context));
	}

	public ServletContext getServletContext() {
		return servletContext;
	}

	public Object getComponent(Object key) throws ComponentNotFoundException {
		try {
			if (applicationContext == null) {
				log.fatal("Spring Application context has not been set");
				throw new IllegalStateException("Spring Application context has not been set");
			}
			if (key == null) {
				log.error("The component key cannot be null");
				throw new ComponentNotFoundException("The component key cannot be null");
			}
			if (key instanceof Class) {
				// TODO: ljz modified
				String names[] = beanFactory.getBeanNamesForType((Class) key);
				// ljz Commented
				// String names[] = beanFactory.getBeanDefinitionNames((Class)key);
				// beanFactory.getBeanNamesForType(type)
				if (names == null || names.length == 0 || names.length > 1)
					throw new ComponentNotFoundException("The container is unable to resolve single instance of " + ((Class) key).getName() + " number of instances found was: "
							+ names.length);
				key = names[0];
			}
			return beanFactory.getBean(key.toString());
		} catch (BeansException e) {

			throw new ComponentNotFoundException("Failed to find component: " + e.getMessage(), e);
		}
	}

	public Object createComponent(Class clazz) {
		return beanFactory.autowire(clazz, 1, false);
	}

	public void autowireComponent(Object bean) {
		if (beanFactory != null)
			beanFactory.autowireNonSingletonBeanProperties(bean, 1, false);
		else
			log.debug("ApplicationContext is null or has not been set. Cannot proceed with autowiring of component: " + bean);
	}

	public void setApplicationContext(ApplicationContext appContext) throws ApplicationContextException {
		applicationContext = appContext;
		beanFactory = new BucketListableBeanFactory(appContext);
	}

	public synchronized void refresh() {
		ContextLoader loader = new ContextLoader();
		ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
		if (ctx != null)
			loader.closeWebApplicationContext(servletContext);
		loader.initWebApplicationContext(servletContext);
		if (applicationContext == null)
			setApplicationContext(WebApplicationContextUtils.getWebApplicationContext(servletContext));
		contextReloaded();
	}

	public boolean isSetup() {
		return applicationContext != null;
	}

	protected void contextReloaded() {
		if (applicationContext != null)
			applicationContext.publishEvent(new ContainerContextLoadedEvent(applicationContext));
	}

	protected ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void publishEvent(Event e) {
		applicationContext.publishEvent(e);
	}

	private static class BucketListableBeanFactory extends DefaultListableBeanFactory {

		public void autowireNonSingletonBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException {
			if (autowireMode != super.AUTOWIRE_BY_NAME && autowireMode != super.AUTOWIRE_BY_TYPE) {
				throw new IllegalArgumentException("Just constants AUTOWIRE_BY_NAME and AUTOWIRE_BY_TYPE allowed");
			} else {
				RootBeanDefinition bd = new RootBeanDefinition(existingBean.getClass(), autowireMode, dependencyCheck);
				bd.setSingleton(false);
				populateBean(existingBean.getClass().getName(), bd, new BeanWrapperImpl(existingBean));
				return;
			}
		}

		public String toString() {
			return "toString overridden for performance reasons";
		}

		public BucketListableBeanFactory(ApplicationContext context) {
			super(context);
		}
	}
}